home *** CD-ROM | disk | FTP | other *** search
/ PCGUIA 127 / PC Guia 127.iso / Software / Utils / GParted Live CD / Bin / gparted-livecd-0.2.2.iso / usr_sqfs / bin / multixterm < prev    next >
Encoding:
Text File  |  2005-07-18  |  29.7 KB  |  991 lines

  1. #!/bin/sh
  2. # \
  3. exec expectk "$0" ${1+"$@"}
  4. #
  5. # NAME
  6. #    multixterm - drive multiple xterms separately or together
  7. #
  8. # SYNOPSIS
  9. #    multixterm [-xa "xterm args"]
  10. #           [-xc "command"]
  11. #           [-xd "directory"]
  12. #           [-xf "file"]
  13. #           [-xn "xterm names"]
  14. #           [-xv] (enable verbose mode)
  15. #           [-xh] or [-x?] (help)
  16. #           [xterm names or user-defined args...]
  17. #
  18. # DESCRIPTION
  19. #    Multixterm creates multiple xterms that can be driven together
  20. #    or separately.
  21. #
  22. #    In its simplest form, multixterm is run with no arguments and
  23. #    commands are interactively entered in the first entry field.
  24. #    Press return (or click the "new xterm" button) to create a new
  25. #    xterm running that command.
  26. #
  27. #    Keystrokes in the "stdin window" are redirected to all xterms
  28. #    started by multixterm.  xterms may be driven separately simply
  29. #    by focusing on them.
  30. #
  31. #    The stdin window must have the focus for keystrokes to be sent
  32. #    to the xterms.  When it has the focus, the color changes to
  33. #    aquamarine.  As characters are entered, the color changes to
  34. #    green for a second.  This provides feedback since characters
  35. #    are not echoed in the stdin window.
  36. #
  37. #    Typing in the stdin window while holding down the alt or meta
  38. #    keys sends an escape character before the typed characters.
  39. #    This provides support for programs such as emacs.
  40. #
  41. # ARGUMENTS
  42. #    The optional -xa argument indicates arguments to pass to
  43. #    xterm.
  44. #
  45. #    The optional -xc argument indicates a command to be run in
  46. #    each named xterm (see -xn).  With no -xc argument, the command
  47. #    is the current shell.
  48. #
  49. #    The optional -xd argument indicates a directory to search for
  50. #    files that will appear in the Files menu.  By default, the
  51. #    directory is: ~/lib/multixterm
  52. #
  53. #    The optional -xf argument indicates a file to be read at
  54. #    startup.  See FILES below for more info.
  55. #
  56. #    The optional -xn argument indicates a name for each xterm.
  57. #    This name will also be substituted for any %n in the command
  58. #    argument (see -xc).
  59. #
  60. #    The optional -xv flag puts multixterm into a verbose mode
  61. #    where it will describe some of the things it is doing
  62. #    internally.  The verbose output is not intended to be
  63. #    understandable to anyone but the author.
  64. #
  65. #    Less common options may be changed by the startup file (see
  66. #    FILES below).
  67. #
  68. #    All the usual X and wish flags are supported (i.e., -display,
  69. #    -name).  There are so many of them that to avoid colliding and
  70. #    make them easy to remember, all the multixterm flags begin
  71. #    with -x.
  72. #
  73. #    If any arguments do not match the flags above, the remainder
  74. #    of the command line is made available for user processing.  By
  75. #    default, the remainder is used as a list of xterm names in the
  76. #    style of -xn. The default behavior may be changed using the
  77. #    .multixtermrc file (see DOT FILE below).
  78. #
  79. # EXAMPLE COMMAND LINE ARGUMENTS
  80. #    The following command line starts up two xterms using ssh to
  81. #    the hosts bud and dexter.
  82. #
  83. #        multixterm -xc "ssh %n" bud dexter
  84. #
  85. # FILES
  86. #    Command files may be used to drive or initialize multixterm.
  87. #    The File menu may be used to invoke other files.  If files
  88. #    exist in the command file directory (see -xd above), they will
  89. #    appear in the File menu.  Files may also be loaded by using
  90. #    File->Open.  Any filename is acceptable but the File->Open
  91. #    browser defaults to files with a .mxt suffix.
  92. #
  93. #    Files are written in Tcl and may change any variables or
  94. #    invoke any procedures.  The primary variables of interest are
  95. #    'xtermCmd' which identifies the command (see -xc) and
  96. #    'xtermNames' which is a list of names (see -xn).  The
  97. #    procedure xtermStartAll, starts xterms for each name in the
  98. #    list.  Other variables and procedures may be discovered by
  99. #    examining multixterm itself.
  100. #
  101. # EXAMPLE FILE
  102. #    The following file does the same thing as the earlier example
  103. #    command line:
  104. #
  105. #        # start two xterms connected to bud and dexter
  106. #        set xtermCmd "ssh %n"
  107. #        set xtermNames {bud dexter}
  108. #        xtermStartAll
  109. #
  110. # DOT FILE
  111. #    At startup, multixterm reads ~/.multixtermrc if present.  This
  112. #    is similar to the command files (see FILES above) except that
  113. #    .multixtermrc may not call xtermStartAll.  Instead it is
  114. #    called implicitly, similar to the way that it is implicit in
  115. #    the command line use of -xn.
  116. #
  117. #    The following example .multixtermrc file makes every xterm run
  118. #    ssh to the hosts named on the command line.
  119. #
  120. #        set xtermCmd "ssh %n"
  121. #
  122. #    Then multixterm could be called simply:
  123. #
  124. #        multixterm bud dexter
  125. #
  126. #    If any command-line argument does not match a multixterm flag,
  127. #    the remainder of the command line is made available to
  128. #    .multixtermrc in the argv variable.  If argv is non-empty when
  129. #    .multixtermrc returns, it is assigned to xtermNames unless
  130. #    xtermNames is non-empty in which case, the content of argv is
  131. #    ignored.
  132. #
  133. #    Commands from .multixtermrc are evaluated early in the
  134. #    initialization of multixterm.  Anything that must be done late
  135. #    in the initialization (such as adding additional bindings to
  136. #    the user interface) may be done by putting the commands inside
  137. #    a procedure called "initLate".
  138. #
  139. # MENUS
  140. #    Except as otherwise noted, the menus are self-explanatory.
  141. #    Some of the menus have dashed lines as the first entry.
  142. #    Clicking on the dashed lines will "tear off" the menus.
  143. #
  144. # USAGE SUGGESTION - ALIASES AND COMMAND FILES
  145. #    Aliases may be used to store lengthy command-line invocations.
  146. #    Command files can be also be used to store such invocations
  147. #    as well as providing a convenient way to share configurations.
  148. #
  149. #    Tcl is a general-purpose language.  Thus multixterm command
  150. #    files can be extremely flexible, such as loading hostnames
  151. #    from other programs or files that may change from day-to-day.
  152. #    In addition, command files can be used for other purposes.
  153. #    For example, command files may be used to prepared common
  154. #    canned interaction sequences.  For example, the command to
  155. #    send the same string to all xterms is:
  156. #
  157. #        xtermSend "a particularly long string"
  158. #
  159. #    The File menu (torn-off) makes canned sequences particularly
  160. #    convenient.  Interactions could also be bound to a mouse
  161. #    button, keystroke, or added to a menu via the .multixtermrc
  162. #    file.
  163. #
  164. # USAGE SUGGESTION - HANDLING MANY XTERMS BY TILING
  165. #    The following .multixtermrc causes tiny xterms to tile across
  166. #    and down the screen.  (You may have to adjust the parameters
  167. #    for your screen.)  This can be very helpful when dealing with
  168. #    large numbers of xterms.
  169. #
  170. #        set yPos 0
  171. #        set xPos 0
  172. #
  173. #        trace variable xtermArgs r traceArgs
  174. #
  175. #        proc traceArgs {args} {
  176. #            global xPos yPos
  177. #            set ::xtermArgs "-geometry 80x12+$xPos+$yPos -font 6x10"
  178. #            if {$xPos} {
  179. #            set xPos 0
  180. #            incr yPos 145
  181. #            if {$yPos > 800} {set yPos 0}
  182. #            } else {
  183. #            set xPos 500
  184. #            }
  185. #        }
  186. #
  187. #    The xtermArgs variable in the code above is the variable
  188. #    corresponding to the -xa argument.
  189. #
  190. #    xterms can be also be created directly.  The following command
  191. #    file creates three xterms overlapped horizontally:
  192. #
  193. #        set xPos 0
  194. #
  195. #        foreach name {bud dexter hotdog} {
  196. #            set ::xtermArgs "-geometry 80x12+$xPos+0 -font 6x10"
  197. #            set ::xtermNames $name
  198. #            xtermStartAll
  199. #            incr xPos 300
  200. #        }
  201. #
  202. # USAGE SUGGESTION - SELECTING HOSTS BY NICKNAME
  203. #    The following .multixtermrc shows an example of changing the
  204. #    default handling of the arguments from hostnames to a filename
  205. #    containing hostnames:
  206. #
  207. #        set xtermNames [exec cat $argv]
  208. #
  209. #    The following is a variation, retrieving the host names from
  210. #    the yp database:
  211. #
  212. #        set xtermNames [exec ypcat $argv]
  213. #
  214. #    The following hardcodes two sets of hosts, so that you can
  215. #    call multixterm with either "cluster1" or "cluster2":
  216. #
  217. #        switch $argv {
  218. #            cluster1 {
  219. #            set xtermNames "bud dexter"
  220. #            }
  221. #            cluster2 {
  222. #            set xtermNames "frank hotdog weiner"
  223. #            }
  224. #        }
  225. #
  226. # COMPARE/CONTRAST
  227. #    It is worth comparing multixterm to xkibitz.  Multixterm
  228. #    connects a separate process to each xterm.  xkibitz connects
  229. #    the same process to each xterm.
  230. #
  231. # LIMITATIONS
  232. #    Multixterm provides no way to remotely control scrollbars,
  233. #    resize, and most other window system related functions.
  234. #
  235. #    Multixterm can only control new xterms that multixterm itself
  236. #    has started.
  237. #
  238. #    As a convenience, the File menu shows a limited number of
  239. #    files.  To show all the files, use File->Open.
  240. #
  241. # FILES
  242. #    $DOTDIR/.multixtermrc   initial command file
  243. #    ~/.multixtermrc         fallback command file
  244. #    ~/lib/multixterm/       default command file directory
  245. #
  246. # BUGS
  247. #    If multixterm is killed using an uncatchable kill, the xterms
  248. #    are not killed.  This appears to be a bug in xterm itself.
  249. #
  250. #    Send/expect sequences can be done in multixterm command files.
  251. #    However, due to the richness of the possibilities, to document
  252. #    it properly would take more time than the author has at present.
  253. #
  254. # REQUIREMENTS
  255. #    Requires Expect 5.36.0 or later.
  256. #    Requires Tk 8.3.3 or later.
  257. #
  258. # VERSION
  259. #!    $::versionString
  260. #    The latest version of multixterm is available from
  261. #    http://expect.nist.gov/example/multixterm .  If your version of Expect
  262. #    and Tk are too old (see REQUIREMENTS above), download a new version of
  263. #    Expect from http://expect.nist.gov
  264. #
  265. # DATE
  266. #!    $::versionDate
  267. #
  268. # AUTHOR
  269. #    Don Libes <don@libes.com>
  270. #
  271. # LICENSE
  272. #    Multixterm is in the public domain; however the author would
  273. #    appreciate acknowledgement if multixterm or parts of it or ideas from
  274. #    it are used.
  275.  
  276. ######################################################################
  277. # user-settable things - override them in the ~/.multixtermrc file
  278. #             or via command-line options
  279. ######################################################################
  280.  
  281. set palette       #d8d8ff   ;# lavender
  282. set colorTyping   green
  283. set colorFocusIn  aquamarine
  284.  
  285. set xtermNames    {}
  286. set xtermCmd      $env(SHELL)
  287. set xtermArgs     ""
  288. set cmdDir      ~/lib/multixterm
  289. set inputLabel    "stdin window"
  290.  
  291. set fileMenuMax   30     ;# max number of files shown in File menu
  292. set tearoffMenuMin 2     ;# min number of files needed to enable the File
  293.              ;# menu to be torn off
  294.  
  295. proc initLate {} {}      ;# anything that must be done late in initialization
  296.              ;# such as adding/modifying bindings, may be done by
  297.              ;# redefining this
  298.  
  299. ######################################################################
  300. # end of user-settable things
  301. ######################################################################
  302.  
  303. ######################################################################
  304. # sanity checking
  305. ######################################################################
  306.  
  307. set versionString 1.8
  308. set versionDate "2004/06/29"
  309.  
  310. package require Tcl
  311. catch {package require Tk} ;# early versions of Tk had no package
  312. package require Expect
  313.  
  314. proc exit1 {msg} {
  315.     puts "multixterm: $msg"
  316.     exit 1
  317. }
  318.  
  319. exp_version -exit 5.36
  320.  
  321. proc tkBad {} {
  322.     exit1 "requires Tk 8.3.3 or later but you are using    Tk $::tk_patchLevel."
  323. }
  324.  
  325. if {$tk_version < 8.3} {
  326.     tkBad
  327. } elseif {$tk_version == 8.3} {
  328.     if {[lindex [split $tk_patchLevel .] 2] < 3} tkBad
  329. }
  330.  
  331. ######################################################################
  332. # process args - has to be done first to get things like -xv working ASAP
  333. ######################################################################
  334.  
  335. # set up verbose mechanism early
  336.  
  337. set verbose 0
  338. proc verbose {msg} {
  339.     if {$::verbose} {
  340.     if {[info level] > 1} {
  341.         set proc [lindex [info level -1] 0]
  342.     } else {
  343.         set proc main
  344.     }
  345.     puts "$proc: $msg"
  346.     }
  347. }
  348.  
  349. # read a single argument from the command line
  350. proc arg_read1 {var args} {
  351.     if {0 == [llength $args]} {
  352.     set argname -$var
  353.     } else {
  354.     set argname $args
  355.     }
  356.  
  357.     upvar argv argv
  358.     upvar $var v
  359.  
  360.     verbose "$argname"
  361.     if {[llength $argv] < 2} {
  362.     exit1 "$argname requires an argument"
  363.     }
  364.  
  365.     set v [lindex $argv 1]
  366.     verbose "set $var $v"
  367.     set argv [lrange $argv 2 end]
  368. }
  369.  
  370. proc xtermUsage {{msg {}}} {
  371.     if {![string equal $msg ""]} {
  372.     puts "multixtermrc: $msg"
  373.     }
  374.     puts {usage: multixterm [flags] ... where flags are:
  375.     [-xa "xterm args"]
  376.     [-xc "command"]
  377.     [-xd "directory"]
  378.     [-xf "file"]
  379.     [-xn "xterm names"]
  380.     [-xv] (enable verbose mode)
  381.     [-xh] or [-x?] (help)
  382.     [xterm names or user-defined args...]}
  383.     exit
  384. }
  385.  
  386. while {[llength $argv]} {
  387.     set flag [lindex $argv 0]
  388.     switch -- $flag -x? - -xh {
  389.     xtermUsage
  390.     } -xc {
  391.     arg_read1 xtermCmd -xc
  392.     } -xn {
  393.     arg_read1 xtermNames -xn
  394.     } -xa {
  395.     arg_read1 xtermArgs -xa
  396.     } -xf {
  397.     arg_read1 cmdFile -xf
  398.     if {![file exists $cmdFile]} {
  399.         exit1 "can't read $cmdFile"
  400.     }
  401.     } -xd {
  402.     arg_read1 cmdDir -xd
  403.     if {![file exists $cmdDir]} {
  404.         exit1 "can't read $cmdDir"
  405.     }
  406.     } -xv {
  407.     set argv [lrange $argv 1 end]
  408.     set verbose 1
  409.     puts "main: verbose on"
  410.     } default {
  411.     verbose "remaining args: $argv"
  412.     break    ;# let user handle remaining args later
  413.     }
  414. }
  415.  
  416. ######################################################################
  417. # determine and load rc file -  has to be done now so that widgets
  418. #     can be affected
  419. ######################################################################
  420.  
  421. # if user has no $DOTDIR, fall back to home directory
  422. if {![info exists env(DOTDIR)]} {
  423.     set env(DOTDIR) ~
  424. }
  425. # catch bogus DOTDIR, otherwise glob will lose the bogus directory
  426. # and it won't appear in the error msg
  427. if {[catch {glob $env(DOTDIR)} dotdir]} {
  428.     exit1 "$env(DOTDIR)/.multixtermrc can't be found because $env(DOTDIR) doesn't exist or can't be read"
  429. set rcFile $dotdir/.multixtermrc
  430.  
  431. set fileTypes {
  432.     {{Multixterm Files} *.mxt}
  433.     {{All Files} *}
  434. }
  435.  
  436. proc openFile {{fn {}}} {
  437.     verbose "opening $fn"
  438.     if {[string equal $fn ""]} {
  439.     set fn [tk_getOpenFile \
  440.             -initialdir $::cmdDir \
  441.             -filetypes $::fileTypes \
  442.             -title "multixterm file"]
  443.     if {[string match $fn ""]} return
  444.     }
  445.     uplevel #0 source [list $fn]
  446.     verbose "xtermNames = \"$::xtermNames\""
  447.     verbose "xtermCmd = $::xtermCmd"
  448. }
  449.  
  450. if {[file exists $rcFile]} {
  451.     openFile $rcFile
  452. } else {
  453.     verbose "$rcFile: not found"
  454. }
  455.  
  456. if {![string equal "" $argv]} {
  457.     if {[string equal $xtermNames ""]} {
  458.     set xtermNames $argv
  459.     }
  460. }
  461.  
  462. ######################################################################
  463. # Describe and initialize some important globals
  464. ######################################################################
  465.  
  466. # ::activeList and ::activeArray both track which xterms to send
  467. # (common) keystrokes to.  Each element in activeArray is connected to
  468. # the active menu.  The list version is just a convenience making the
  469. # send function easier/faster.
  470.  
  471. set activeList {}
  472.  
  473. # ::names is an array of xterm names indexed by process spawn ids.
  474.  
  475. set names(x) ""
  476. unset names(x)
  477.  
  478. # ::xtermSid is an array of xterm spawn ids indexed by process spawn ids.
  479. # ::xtermPid is an array of xterm pids indexed by process spawn id.
  480.  
  481. ######################################################################
  482. # create an xterm and establish connections
  483. ######################################################################
  484.  
  485. proc xtermStart {cmd name} {
  486.     verbose "starting new xterm running $cmd with name $name"
  487.  
  488.     ######################################################################
  489.     # create pty for xterm
  490.     ######################################################################
  491.     set pid [spawn -noecho -pty]
  492.     verbose "spawn -pty: pid = $pid, spawn_id = $spawn_id"
  493.     set sidXterm $spawn_id
  494.     stty raw -echo < $spawn_out(slave,name)
  495.  
  496.     regexp ".*(.)(.)" $spawn_out(slave,name) dummy c1 c2
  497.     if {[string compare $c1 "/"] == 0} {
  498.     set c1 0
  499.     }
  500.  
  501.     ######################################################################
  502.     # prepare to start xterm by making sure xterm name is unique
  503.     # X doesn't care but active menu won't make sense unless names are unique
  504.     ######################################################################
  505.     set unique 1
  506.     foreach oldName [array names ::names] {
  507.     if {[string match "$name" $::names($oldName)]} {
  508.         set unique 0
  509.     }
  510.     }
  511.     verbose "uniqueness of $name: $unique"
  512.  
  513.     set safe [safe $name]
  514.  
  515.     # if not unique, look at the numerical suffixes of all matching
  516.     # names, find the biggest and increment it
  517.     if {!$unique} {
  518.     set suffix 2
  519.     foreach oldName [array names ::names] {
  520.         verbose "regexp ^[set safe](\[0-9]+)$ $::names($oldName) X num"
  521.         if {[regexp "^[set safe](\[0-9]+)$" $::names($oldName) X num]} {
  522.         verbose "matched, checking suffix"
  523.         if {$num >= $suffix} {
  524.             set suffix [expr $num+1]
  525.             verbose "new suffix: $suffix"
  526.         }
  527.         }
  528.     }
  529.     append name $suffix
  530.     verbose "new name: $name"
  531.     }
  532.  
  533.     ######################################################################
  534.     # start new xterm
  535.     ######################################################################
  536.     set xtermpid [eval exec xterm -name [list $name] -S$c1$c2$spawn_out(slave,fd) $::xtermArgs &]
  537.     verbose "xterm: pid = $xtermpid"
  538.     close -slave
  539.  
  540.     # xterm first sends back window id, save in environment so it can be
  541.     # passed on to the new process
  542.     log_user 0
  543.     expect {
  544.     eof {wait;return}
  545.     -re (.*)\n {
  546.         # convert hex to decimal
  547.         # note quotes must be used here to avoid diagnostic from expr
  548.         set ::env(WINDOWID) [expr "0x$expect_out(1,string)"]
  549.     }
  550.     }
  551.  
  552.     ######################################################################
  553.     # start new process
  554.     ######################################################################
  555.     set pid [eval spawn -noecho $cmd]
  556.     verbose "$cmd: pid = $pid, spawn_id = $spawn_id"
  557.     set sidCmd $spawn_id
  558.     lappend ::activeList $sidCmd
  559.     set ::activeArray($sidCmd) 1
  560.  
  561.     ######################################################################
  562.     # link everything back to spawn id of new process
  563.     ######################################################################
  564.     set ::xtermSid($sidCmd) $sidXterm
  565.     set ::names($sidCmd)    $name
  566.     set ::xtermPid($sidCmd) $xtermpid
  567.  
  568.     ######################################################################
  569.     # connect proc output to xterm output
  570.     # connect xterm input to proc input
  571.     ######################################################################
  572.     expect_background {
  573.     -i $sidCmd
  574.     -re ".+" [list sendTo $sidXterm]
  575.     eof [list xtermKill $sidCmd]
  576.     -i $sidXterm
  577.     -re ".+" [list sendTo $sidCmd]
  578.     eof [list xtermKill $sidCmd]
  579.     }
  580.  
  581.     .m.e entryconfig Active -state normal
  582.     .m.e.active add checkbutton -label $name -variable activeArray($sidCmd) \
  583.     -command [list xtermActiveUpdate $sidCmd]
  584.     set ::activeArray($sidCmd) 1
  585. }
  586.  
  587. proc xtermActiveUpdate {sid} {
  588.     if {$::activeArray($sid)} {
  589.     verbose "activating $sid"
  590.     } else {
  591.     verbose "deactivating $sid"
  592.     }
  593.     activeListUpdate
  594. }
  595.  
  596. proc activeListUpdate {} {
  597.     set ::activeList {}
  598.     foreach n [array names ::activeArray] {
  599.     if {$::activeArray($n)} {
  600.         lappend ::activeList $n
  601.     }
  602.     }
  603. }
  604.  
  605. # make a string safe to go through regexp
  606. proc safe {s} {
  607.     string map {{[} {\[} {*} {\*} {+} {\+} {^} {\^} {$} {\\$}} $s
  608. }
  609.  
  610. # utility to map xterm name to spawn id
  611. # multixterm doesn't use this but a user might want to
  612. proc xtermGet {name} {
  613.     foreach sid [array names ::names] {
  614.     if {[string equal $name $::names($sid)]} {
  615.         return $sid
  616.     }
  617.     }
  618.     error "no such term with name: $name"
  619. }
  620.  
  621. # utility to activate an xterm
  622. # multixterm doesn't use this but a user might want to
  623. proc xtermActivate {sid} {
  624.     set ::activeArray($sid) 1
  625.     xtermActiveUpdate $sid
  626. }
  627.  
  628. # utility to deactivate an xterm
  629. # multixterm doesn't use this but a user might want to
  630. proc xtermDeactivate {sid} {
  631.     set ::activeArray($sid) 0
  632.     xtermActiveUpdate $sid
  633. }
  634.  
  635. # utility to do an explicit Expect
  636. # multixterm doesn't use this but a user might want to
  637. proc xtermExpect {args} {
  638.     # check if explicit spawn_id in args
  639.     for {set i 0} {$i < [llength $args]} {incr i} {
  640.     switch -- [lindex $args $i] "-i" {
  641.         set sidCmd [lindex $args [incr i]]
  642.         break
  643.     }
  644.     }
  645.  
  646.     if {![info exists sidCmd]} {
  647.     # nothing explicit, so get it from the environment
  648.  
  649.     upvar spawn_id spawn_id
  650.  
  651.     # mimic expect's normal behavior in obtaining spawn_id
  652.     if {[info exists spawn_id]} {
  653.         set sidCmd $spawn_id
  654.     } else {
  655.         set sidCmd $::spawn_id
  656.     }
  657.     }
  658.  
  659.     # turn off bg expect, do fg expect, then re-enable bg expect
  660.  
  661.     expect_background -i $sidCmd    ;# disable bg expect
  662.     eval expect $args            ;# fg expect
  663.                     ;# reenable bg expect
  664.     expect_background {
  665.     -i $sidCmd
  666.     -re ".+" [list sendTo $::xtermSid($sidCmd)]
  667.     eof [list xtermKill $sidCmd]
  668.     }
  669. }
  670.  
  671. ######################################################################
  672. # connect main window keystrokes to all xterms
  673. ######################################################################
  674. proc xtermSend {A} {
  675.     if {[info exists ::afterId]} {
  676.     after cancel $::afterId
  677.     }
  678.     .input config -bg $::colorTyping
  679.     set ::afterId [after 1000 {.input config -bg $colorCurrent}]
  680.  
  681.     exp_send -raw -i $::activeList -- $A
  682. }
  683.  
  684. proc sendTo {to} {
  685.     exp_send -raw -i $to -- $::expect_out(buffer)
  686. }
  687.  
  688. # catch the case where there's no selection
  689. proc xtermPaste {} {catch {xtermSend [selection get]}}
  690.  
  691. ######################################################################
  692. # clean up an individual process death or xterm death
  693. ######################################################################
  694. proc xtermKill {s} {
  695.     verbose "killing xterm $s"
  696.  
  697.     if {![info exists ::xtermPid($s)]} {
  698.     verbose "too late, already dead"
  699.     return
  700.     }
  701.  
  702.     catch {exec /bin/kill -9 $::xtermPid($s)}
  703.     unset ::xtermPid($s)
  704.  
  705.     # remove sid from activeList
  706.     verbose "removing $s from active array"
  707.     catch {unset ::activeArray($s)}
  708.     activeListUpdate
  709.  
  710.     verbose "removing from background handler $s"
  711.     catch {expect_background -i $s}
  712.     verbose "removing from background handler $::xtermSid($s)"
  713.     catch {expect_background -i $::xtermSid($s)}
  714.     verbose "closing proc"
  715.     catch {close -i $s}
  716.     verbose "closing xterm"
  717.     catch {close -i $::xtermSid($s)}
  718.     verbose "waiting on proc"
  719.     wait -i $s
  720.     wait -i $::xtermSid($s)
  721.     verbose "done waiting"
  722.     unset ::xtermSid($s)
  723.  
  724.     # remove from active menu
  725.     verbose "deleting active menu entry $::names($s)"
  726.  
  727.     # figure out which it is
  728.     # avoid using name as an index since we haven't gone to any pains to
  729.     # make it safely interpreted by index-pattern code.  instead step
  730.     # through, doing the comparison ourselves
  731.     set last [.m.e.active index last]
  732.     # skip over tearoff
  733.     for {set i 1} {$i <= $last} {incr i} {
  734.     if {![catch {.m.e.active entrycget $i -label} label]} {
  735.         if {[string equal $label $::names($s)]} break
  736.     }
  737.     }
  738.     .m.e.active delete $i
  739.     unset ::names($s)
  740.  
  741.     # if none left, disable menu
  742.     # this leaves tearoff clone but that seems reasonable
  743.     if {0 == [llength [array names ::xtermSid]]} {
  744.     .m.e entryconfig Active -state disable
  745.     }
  746. }
  747.  
  748. ######################################################################
  749. # create windows
  750. ######################################################################
  751. tk_setPalette $palette
  752.  
  753. menu .m -tearoff 0
  754. .m add cascade -menu .m.f    -label "File" -underline 0
  755. .m add cascade -menu .m.e    -label "Edit" -underline 0
  756. .m add cascade -menu .m.help -label "Help" -underline 0
  757. set files [glob -nocomplain $cmdDir/*]
  758. set filesLength [llength $files]
  759. if {$filesLength >= $tearoffMenuMin} {
  760.     set filesTearoff 1
  761. } else {
  762.     set filesTearoff 0
  763. }
  764. menu .m.f    -tearoff $filesTearoff -title "multixterm files"
  765. menu .m.e    -tearoff 0
  766. menu .m.help -tearoff 0
  767. .m.f    add command -label Open -command openFile -underline 0
  768.  
  769. if {$filesLength} {
  770.     .m.f add separator
  771.     set files [lsort $files]
  772.     set files [lrange $files 0 $fileMenuMax]
  773.     foreach f $files {
  774.     .m.f add command -label $f -command [list openFile $f]
  775.     }
  776.     .m.f add separator
  777. }
  778.  
  779. .m.f    add command -label "Exit"     -command exit       -underline 0
  780. .m.e    add command -label "Paste"    -command xtermPaste -underline 0
  781. .m.e    add cascade -label "Active"   -menu .m.e.active   -underline 0
  782. .m.help add command -label "About"    -command about      -underline 0
  783. .m.help add command -label "Man Page" -command help       -underline 0
  784. . config -m .m
  785.  
  786. menu .m.e.active -tearoff 1 -title "multixterm active"
  787. .m.e entryconfig Active -state disabled
  788. # disable the Active menu simply because it looks goofy seeing an empty menu
  789. # for consistency, though, it should be enabled
  790.  
  791. entry  .input -textvar inputLabel -justify center -state disabled
  792. entry  .cmd   -textvar xtermCmd
  793. button .exec  -text "new xterm" -command {xtermStart $xtermCmd $xtermCmd}
  794.  
  795. grid .input -sticky ewns
  796. grid .cmd   -sticky ew
  797. grid .exec  -sticky ew -ipadx 3 -ipady 3
  798.  
  799. grid columnconfigure . 0 -weight 1
  800. grid    rowconfigure . 0 -weight 1  ;# let input window only expand
  801.  
  802. bind .cmd   <Return>        {xtermStart $xtermCmd $xtermCmd}
  803.  
  804. # send all keypresses to xterm 
  805. bind .input <KeyPress>         {xtermSend %A ; break}
  806. bind .input <Alt-KeyPress>     {xtermSend \033%A; break}
  807. bind .input <Meta-KeyPress>    {xtermSend \033%A; break}
  808. bind .input <<Paste>>          {xtermPaste ; break}
  809. bind .input <<PasteSelection>> {xtermPaste ; break}
  810.  
  811. # arrow keys - note that if they've been rebound through .Xdefaults
  812. # you'll have to change these definitions.
  813. bind .input <Up>    {xtermSend \033OA; break}
  814. bind .input <Down>  {xtermSend \033OB; break}
  815. bind .input <Right> {xtermSend \033OC; break}
  816. bind .input <Left>  {xtermSend \033OD; break}
  817. # Strange: od -c reports these as \033[A et al but when keypad mode
  818. # is initialized, they send \033OA et al.  Presuming most people
  819. # want keypad mode, I'll go with the O versions.  Perhaps the other
  820. # version is just a Sun-ism anyway.
  821.  
  822. set colorCurrent [.input cget -bg]
  823. set colorFocusOut $colorCurrent
  824.  
  825. # change color to show focus
  826. bind .input <FocusOut> colorFocusOut
  827. bind .input <FocusIn>  colorFocusIn
  828. proc colorFocusIn  {} {.input config -bg [set ::colorCurrent $::colorFocusIn]}
  829. proc colorFocusOut {} {.input config -bg [set ::colorCurrent $::colorFocusOut]}
  830.  
  831. # convert normal mouse events to focusIn
  832. bind .input <1>       {focus .input; break}
  833. bind .input <Shift-1> {focus .input; break}
  834.  
  835. # ignore all other mouse events that might make selection visible
  836. bind .input <Double-1>  break
  837. bind .input <Triple-1>  break
  838. bind .input <B1-Motion> break
  839. bind .input <B2-Motion> break
  840.  
  841. set scriptName [info script] ;# must get while it's active
  842.  
  843. proc about {} {
  844.     set w .about
  845.     if {[winfo exists $w]} {
  846.     wm deiconify $w
  847.     raise $w
  848.     return
  849.     }
  850.     toplevel     $w
  851.     wm title     $w "about multixterm"
  852.     wm iconname  $w "about multixterm"
  853.     wm resizable $w 0 0
  854.  
  855.     button $w.b -text Dismiss -command [list wm withdraw $w]
  856.  
  857.     label $w.title -text "multixterm" -font "Times 16" -borderwidth 10 -fg red
  858.     label $w.version -text "Version $::versionString, Released $::versionDate"
  859.     label $w.author -text "Written by Don Libes <don@libes.com>"
  860.     label $w.using -text "Using Expect [exp_version],\
  861.                                 Tcl $::tcl_patchLevel,\
  862.                                 Tk $::tk_patchLevel"
  863.     grid $w.title
  864.     grid $w.version
  865.     grid $w.author
  866.     grid $w.using
  867.     grid $w.b -sticky ew
  868. }
  869.  
  870. proc help {} {
  871.     if {[winfo exists .help]} {
  872.     wm deiconify .help
  873.     raise .help
  874.     return
  875.     }
  876.     toplevel    .help
  877.     wm title    .help "multixterm help"
  878.     wm iconname .help "multixterm help"
  879.  
  880.     scrollbar .help.sb -command {.help.text yview}
  881.     text .help.text -width 74 -height 30 -yscroll {.help.sb set} -wrap word
  882.  
  883.     button .help.ok -text Dismiss -command {destroy .help} -relief raised
  884.     bind .help <Return> {destroy .help;break}
  885.     grid .help.sb   -row 0 -column 0     -sticky ns
  886.     grid .help.text -row 0 -column 1     -sticky nsew
  887.     grid .help.ok   -row 1 -columnspan 2 -sticky ew -ipadx 3 -ipady 3
  888.  
  889.     # let text box only expand
  890.     grid rowconfigure    .help 0 -weight 1
  891.     grid columnconfigure .help 1 -weight 1
  892.  
  893.     set script [auto_execok $::scriptName]
  894.     if {[llength $script] == 0} {
  895.     set script /depot/tcl/bin/multixterm     ;# fallback
  896.     }
  897.     if {[catch {open $script} fid]} {
  898.     .help.text insert end "Could not open help file: $script"
  899.     } else {
  900.     # skip to the beginning of the actual help (starts with "NAME")
  901.     while {-1 != [gets $fid buf]} {
  902.         if {1 == [regexp "NAME" $buf]} {
  903.         .help.text insert end "\n NAME\n"
  904.         break
  905.         }
  906.     }
  907.     
  908.     while {-1 != [gets $fid buf]} {
  909.         if {0 == [regexp "^#(.?)(.*)" $buf X key buf]} break
  910.         if {$key == "!"} {
  911.         set buf [subst -nocommands $buf]
  912.         set key " "
  913.         }
  914.         .help.text insert end $key$buf\n
  915.     }
  916.     }
  917.  
  918.     # support scrolling beyond Tk's built-in Next/Previous
  919.     foreach w {"" .sb .text .ok} {
  920.     set W .help$w
  921.     bind $W <space>     {scrollPage  1}  ;#more
  922.     bind $W <Delete>     {scrollPage -1}  ;#more
  923.     bind $W <BackSpace>     {scrollPage -1}  ;#more
  924.     bind $W <Control-v>    {scrollPage  1}  ;#emacs
  925.     bind $W <Meta-v>    {scrollPage -1}  ;#emacs
  926.     bind $W <Control-f>    {scrollPage  1}  ;#vi
  927.     bind $W <Control-b>    {scrollPage -1}  ;#vi
  928.     bind $W <F35>        {scrollPage  1}  ;#sun
  929.     bind $W <F29>        {scrollPage -1}  ;#sun
  930.     bind $W <Down>            {scrollLine  1}
  931.     bind $W <Up>        {scrollLine -1}
  932.     }
  933. }
  934.  
  935. proc scrollPage {dir} {
  936.     tkScrollByPages .help.sb v $dir
  937.     return -code break
  938. }
  939.  
  940. proc scrollLine {dir} {
  941.     tkScrollByUnits .help.sb v $dir
  942.     return -code break
  943. }
  944.  
  945. ######################################################################
  946. # exit handling
  947. ######################################################################
  948.  
  949. # xtermKillAll is not intended to be user-callable.  It just kills
  950. # the processes and that's it. A user-callable version would update
  951. # the data structures, close the channels, etc.
  952.  
  953. proc xtermKillAll {} {
  954.     foreach sid [array names ::xtermPid] {
  955.     exec /bin/kill -9 $::xtermPid($sid)
  956.     }
  957. }
  958.  
  959. rename exit _exit
  960. proc exit {{x 0}} {xtermKillAll;_exit $x}
  961.  
  962. wm protocol . WM_DELETE_WINDOW exit
  963. trap exit SIGINT
  964.  
  965. ######################################################################
  966. # start any xterms requested
  967. ######################################################################
  968. proc xtermStartAll {} {
  969.     verbose "xtermNames = \"$::xtermNames\""
  970.     foreach n $::xtermNames {
  971.     regsub -all "%n" $::xtermCmd $n cmdOut
  972.     xtermStart $cmdOut $n
  973.     }
  974.     set ::xtermNames {}
  975. }
  976.  
  977. initLate
  978.  
  979. # now that xtermStartAll and its accompanying support has been set up
  980. # run it to start anything defined by rc file or command-line args.
  981.  
  982. xtermStartAll     ;# If nothing has been requested, this is a no-op.
  983.  
  984. # finally do any explicit command file
  985. if {[info exists cmdFile]} {
  986.     openFile $cmdFile
  987. }
  988.  
  989. puts hello
  990.